//
// networking protocol support (IP, UDP, ARP, etc.).
//

#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "net.h"
#include "defs.h"

static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15); // qemu's idea of the guest IP
static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
static uint8 broadcast_mac[ETHADDR_LEN] = { 0xFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF };

// Strips data from the start of the buffer and returns a pointer to it.
// Returns 0 if less than the full requested length is available.
char *
mbufpull(struct mbuf *m, unsigned int len)
{
  char *tmp = m->head;
  if (m->len < len)
    return 0;
  m->len -= len;
  m->head += len;
  return tmp;
}

// Prepends data to the beginning of the buffer and returns a pointer to it.
char *
mbufpush(struct mbuf *m, unsigned int len)
{
  m->head -= len;
  if (m->head < m->buf)
    panic("mbufpush");
  m->len += len;
  return m->head;
}

// Appends data to the end of the buffer and returns a pointer to it.
char *
mbufput(struct mbuf *m, unsigned int len)
{
  char *tmp = m->head + m->len;
  m->len += len;
  if (m->len > MBUF_SIZE)
    panic("mbufput");
  return tmp;
}

// Strips data from the end of the buffer and returns a pointer to it.
// Returns 0 if less than the full requested length is available.
char *
mbuftrim(struct mbuf *m, unsigned int len)
{
  if (len > m->len)
    return 0;
  m->len -= len;
  return m->head + m->len;
}

// Allocates a packet buffer.
struct mbuf *
mbufalloc(unsigned int headroom)
{
  struct mbuf *m;
 
  if (headroom > MBUF_SIZE)
    return 0;
  m = kalloc();
  if (m == 0)
    return 0;
  m->next = 0;
  m->head = (char *)m->buf + headroom;
  m->len = 0;
  return m;
}

// Frees a packet buffer.
void
mbuffree(struct mbuf *m)
{
  kfree(m);
}

// Pushes an mbuf to the end of the queue.
void
mbufq_pushtail(struct mbufq *q, struct mbuf *m)
{
  m->next = 0;
  if (!q->head){
    q->head = q->tail = m;
    return;
  }
  q->tail->next = m;
  q->tail = m;
}

// Pops an mbuf from the start of the queue.
struct mbuf *
mbufq_pophead(struct mbufq *q)
{
  struct mbuf *head = q->head;
  if (!head)
    return 0;
  q->head = head->next;
  if (!q->head)
    q->tail = 0;
  head->next = 0;
  return head;
}

// Returns one (nonzero) if the queue is empty.
int
mbufq_empty(struct mbufq *q)
{
  return q->head == 0;
}

// Intializes a queue of mbufs.
void
mbufq_init(struct mbufq *q)
{
  q->head = 0;
  q->tail = 0;
}

// This code is lifted from FreeBSD's ping.c, and is copyright by the Regents
// of the University of California.
static unsigned short
in_cksum(const unsigned char *addr, int len)
{
  int nleft = len;
  const unsigned short *w = (const unsigned short *)addr;
  unsigned int sum = 0;
  unsigned short answer = 0;

  /*
   * Our algorithm is simple, using a 32 bit accumulator (sum), we add
   * sequential 16 bit words to it, and at the end, fold back all the
   * carry bits from the top 16 bits into the lower 16 bits.
   */
  while (nleft > 1)  {
    sum += *w++;
    nleft -= 2;
  }

  /* mop up an odd byte, if necessary */
  if (nleft == 1) {
    *(unsigned char *)(&answer) = *(const unsigned char *)w;
    sum += answer;
  }

  /* add back carry outs from top 16 bits to low 16 bits */
  sum = (sum & 0xffff) + (sum >> 16);
  sum += (sum >> 16);
  /* guaranteed now that the lower 16 bits of sum are correct */

  answer = ~sum; /* truncate to 16 bits */
  return answer;
}

static unsigned short
icmp_cksum(const unsigned char *addr, int len)
{
  int nleft = len;
  const unsigned short *w = (const unsigned short *)addr;
  unsigned int sum = 0;
  unsigned short answer = 0;

  /*
   * Our algorithm is simple, using a 32 bit accumulator (sum), we add
   * sequential 16 bit words to it, and at the end, fold back all the
   * carry bits from the top 16 bits into the lower 16 bits.
   */
  while (nleft > 1)  {
    sum += *w++;
    nleft -= 2;
  }

  /* mop up an odd byte, if necessary */
  if (nleft == 1) {
    *(unsigned char *)(&answer) = *(const unsigned char *)w;
    sum += (answer<<8);
  }

  /* add back carry outs from top 16 bits to low 16 bits */
  sum = (sum & 0xffff) + (sum >> 16);
  sum += (sum >> 16);
  /* guaranteed now that the lower 16 bits of sum are correct */

  answer = ~sum; /* truncate to 16 bits */
  return answer;
}

// sends an ethernet packet
static void
net_tx_eth(struct mbuf *m, uint16 ethtype)
{
  struct eth *ethhdr;

  ethhdr = mbufpushhdr(m, *ethhdr);
  memmove(ethhdr->shost, local_mac, ETHADDR_LEN);
  // In a real networking stack, dhost would be set to the address discovered
  // through ARP. Because we don't support enough of the ARP protocol, set it
  // to broadcast instead.
  memmove(ethhdr->dhost, broadcast_mac, ETHADDR_LEN);
  ethhdr->type = htons(ethtype);
  if (e1000_transmit(m)) {
    mbuffree(m);
  }
}

// sends an IP packet
static void
net_tx_ip(struct mbuf *m, uint8 proto, uint32 dip)
{
  struct ip *iphdr;

  // push the IP header
  iphdr = mbufpushhdr(m, *iphdr);
  memset(iphdr, 0, sizeof(*iphdr));
  iphdr->ip_vhl = (4 << 4) | (20 >> 2);
  iphdr->ip_p = proto;
  iphdr->ip_src = htonl(local_ip);
  iphdr->ip_dst = htonl(dip);
  iphdr->ip_len = htons(m->len);
  iphdr->ip_ttl = 100;
  iphdr->ip_sum = in_cksum((unsigned char *)iphdr, sizeof(*iphdr));

  // now on to the ethernet layer
  net_tx_eth(m, ETHTYPE_IP);
}

// sends a ICMP
void
net_tx_icmp(uint32 dip)
{
  struct mbuf *m=mbufalloc(MBUF_DEFAULT_HEADROOM);
  if (!m)
    return;
  struct icmp *icmphdr;
  icmphdr = mbufpushhdr(m, *icmphdr);
  icmphdr->type=8;
  icmphdr->code=0;
  icmphdr->identify=0;
  icmphdr->seq=0;
  icmphdr->checksum=0;
  // imcp的校验和是将icmp报文段没16位加和，最后结果为0xFFFF-求和
  icmphdr->checksum = icmp_cksum((const unsigned char *)icmphdr, sizeof(*icmphdr));
  // now on to the IP layer
  net_tx_ip(m, IPPROTO_ICMP, dip);
}

// sends a UDP packet
void
net_tx_udp(struct mbuf *m, uint32 dip,
           uint16 sport, uint16 dport)
{
  struct udp *udphdr;

  // put the UDP header
  udphdr = mbufpushhdr(m, *udphdr);
  udphdr->sport = htons(sport);
  udphdr->dport = htons(dport);
  udphdr->ulen = htons(m->len);
  udphdr->sum = 0; // zero means no checksum is provided

  // now on to the IP layer
  net_tx_ip(m, IPPROTO_UDP, dip);
}

// 构成为伪ip头，共12字节：对方IP+我方IP+零及协议号0x0006+报文长度(2字节)
// 校验和, tcp校验和公式：伪ip头+TCP各字段长度（校验和需预设为0）+ 数据各字段和
uint16 checksumcompute(uint32 dip, uint32 sip , uint16 proto, uint16 plen, uint16 *buffer,int size)
{
  uint32 cksum=0;/*32位长整数，检验和被置为0*/

  cksum += (dip>>16) + (dip&0xffff) + (sip>>16) + (sip&0xffff) +
           htons(proto) + htons(plen);

  while (size>1){
    cksum +=*buffer++;
    size -=sizeof(uint16);
  }

  if (size) {
    /*处理剩余下来的字段，这些字段皆小于16位*/
    cksum +=*(uint8 *) buffer;
  }

  /*将32位转换为16位,高16位与低16位相加*/
  while (cksum>>16)
        cksum = (cksum>>16) + (cksum & 0xffff);
  return (uint16) (~cksum);
}

// sends a TCP packet with necessary parameters
void tcp_output(struct mbuf *m, uint32 dip,
           uint16 sport, uint16 dport, uint32 seqnum, uint32 acknum, uint8 flags, uint16 winsize) 
{
  struct tcp *tcphdr;
  printf("send seqnum= %d, acknum = %d, mbuflen=%d\n", seqnum, acknum, m->len);
  // put the TCP header
  tcphdr = mbufpushhdr(m, *tcphdr);
  tcphdr->sport = htons(sport);
  tcphdr->dport = htons(dport);
  tcphdr->seqnum = htonl(seqnum); // todo
  tcphdr->acknum = htonl(acknum); // todo
  tcphdr->reserve = 0;
  tcphdr->hoffset = 20>>2;
  tcphdr->flags = flags;
  tcphdr->winsize = htons(winsize);
  tcphdr->urgent_point = 0; // don't care
  tcphdr->checksum = 0; 
  tcphdr->checksum = checksumcompute(dip, local_ip, IPPROTO_TCP, m->len, (uint16 *)tcphdr, m->len);

  // now on to the IP layer
  net_tx_ip(m, IPPROTO_TCP, dip);
}

// sends a TCP packet
void
net_tx_tcp(struct mbuf *m, uint32 dip,
           uint16 sport, uint16 dport)
{
  exit(-1);
}

// sends an ARP packet
int
net_tx_arp(uint16 op, uint8 dmac[6], uint32 dip)
{
  struct mbuf *m;
  struct arp *arphdr;

  m = mbufalloc(MBUF_DEFAULT_HEADROOM);
  if (!m)
    return -1;

  // generic part of ARP header
  arphdr = mbufputhdr(m, *arphdr);
  arphdr->hrd = htons(ARP_HRD_ETHER);
  arphdr->pro = htons(ETHTYPE_IP);
  arphdr->hln = ETHADDR_LEN;
  arphdr->pln = sizeof(uint32);
  arphdr->op = htons(op);

  // ethernet + IP part of ARP header
  memmove(arphdr->sha, local_mac, ETHADDR_LEN);
  arphdr->sip = htonl(local_ip);
  memmove(arphdr->tha, dmac, ETHADDR_LEN);
  arphdr->tip = htonl(dip);

  // header is ready, send the packet
  net_tx_eth(m, ETHTYPE_ARP);
  return 0;
}

// receives an ARP packet
static void
net_rx_arp(struct mbuf *m)
{
  struct arp *arphdr;
  uint8 smac[ETHADDR_LEN];
  uint32 sip, tip;

  arphdr = mbufpullhdr(m, *arphdr);
  if (!arphdr)
    goto done;

  printf("Receives an ARP packet: sender's IP=%x, MAC=%x:%x:%x:%x:%x:%x\n", 
      ntohl(arphdr->sip), arphdr->sha[0],arphdr->sha[1],arphdr->sha[2],arphdr->sha[3],arphdr->sha[4],arphdr->sha[5]);

  // validate the ARP header
  if (ntohs(arphdr->hrd) != ARP_HRD_ETHER ||
      ntohs(arphdr->pro) != ETHTYPE_IP ||
      arphdr->hln != ETHADDR_LEN ||
      arphdr->pln != sizeof(uint32)) {
    goto done;
  }

  // only requests are supported so far
  // check if our IP was solicited
  tip = ntohl(arphdr->tip); // target IP address
  if (ntohs(arphdr->op) != ARP_OP_REQUEST || tip != local_ip)
    goto done;

  // handle the ARP request
  memmove(smac, arphdr->sha, ETHADDR_LEN); // sender's ethernet address
  sip = ntohl(arphdr->sip); // sender's IP address (qemu's slirp)
  net_tx_arp(ARP_OP_REPLY, smac, sip);

done:
  mbuffree(m);
}

// receives a ICMP packet
static void
net_rx_icmp(struct mbuf *m, uint16 len, struct ip *iphdr)
{
  struct icmp *icmphdr;
  icmphdr = mbufpullhdr(m, *icmphdr);
  if (!icmphdr)
    goto fail;

  if (icmphdr->type==0 && icmphdr->code==0) {
    printf("get icmp ping reponse from %x\n", ntohl(iphdr->ip_src));
  } else if (icmphdr->type==14 && icmphdr->code==0) {
    printf("get icmp time reponse from %x\n", ntohl(iphdr->ip_src));
  } else  {
    printf("get icmp unknown reponse from %x, icmphdr->type=%d, icmphdr->code=%d\n", 
                ntohl(iphdr->ip_src), icmphdr->type, icmphdr->code);
  }

  return;

fail:
  mbuffree(m);
}

// receives a UDP packet
static void
net_rx_udp(struct mbuf *m, uint16 len, struct ip *iphdr)
{
  struct udp *udphdr;
  uint32 sip;
  uint16 sport, dport;


  udphdr = mbufpullhdr(m, *udphdr);
  if (!udphdr)
    goto fail;

  // TODO: validate UDP checksum

  // validate lengths reported in headers
  if (ntohs(udphdr->ulen) != len)
    goto fail;
  len -= sizeof(*udphdr);
  if (len > m->len)
    goto fail;
  // minimum packet size could be larger than the payload
  mbuftrim(m, m->len - len);

  // parse the necessary fields
  sip = ntohl(iphdr->ip_src);
  sport = ntohs(udphdr->sport);
  dport = ntohs(udphdr->dport);
  sockrecvudp(m, sip, dport, sport);
  return;

fail:
  mbuffree(m);
}

// receives a TCP packet
static void
net_rx_tcp(struct mbuf *m, uint16 len, struct ip *iphdr)
{
  struct tcp *tcphdr;
  uint32 sip;
  uint16 sport, dport;

  tcphdr = mbufpullhdr(m, *tcphdr);
  if (!tcphdr)
    goto fail;

  uint32 hdrlen;

  hdrlen = tcphdr->hoffset << 2;
  if (hdrlen < sizeof(*tcphdr) || hdrlen > len)
    goto fail;

  len -= hdrlen;
  if (hdrlen > sizeof(*tcphdr)) {
    if (!mbufpull(m, hdrlen - sizeof(*tcphdr)))
      goto fail;
  }

  if (len > m->len)
    goto fail;
  if (len < m->len)
    mbuftrim(m, m->len - len);

  // parse the necessary fields
  sip = ntohl(iphdr->ip_src);
  sport = ntohs(tcphdr->sport);
  dport = ntohs(tcphdr->dport);
  sockrecvtcp(tcphdr, m, sip, dport, sport, ntohl(tcphdr->seqnum), ntohl(tcphdr->acknum));
  return;

fail:
  mbuffree(m);
}

// receives an IP packet
static void
net_rx_ip(struct mbuf *m)
{
  struct ip *iphdr;
  uint16 len;

  iphdr = mbufpullhdr(m, *iphdr);
  if (!iphdr)
	  goto fail;

  // check IP version and header len
  if (iphdr->ip_vhl != ((4 << 4) | (20 >> 2)))
    goto fail;
  // validate IP checksum
  if (in_cksum((unsigned char *)iphdr, sizeof(*iphdr)))
    goto fail;
  // can't support fragmented IP packets
  if (htons(iphdr->ip_off) != 0)
    goto fail;
  // is the packet addressed to us?
  if (htonl(iphdr->ip_dst) != local_ip)
    goto fail;

  len = ntohs(iphdr->ip_len) - sizeof(*iphdr);
  if (iphdr->ip_p == IPPROTO_TCP) {
    net_rx_tcp(m, len, iphdr);
  } else if (iphdr->ip_p == IPPROTO_UDP){
    net_rx_udp(m, len, iphdr);
  } else if (iphdr->ip_p == IPPROTO_ICMP){
    net_rx_icmp(m, len, iphdr);
  }
  return;

fail:
  mbuffree(m);
}

// called by e1000 driver's interrupt handler to deliver a packet to the
// networking stack
void net_rx(struct mbuf *m)
{
  struct eth *ethhdr;
  uint16 type;

  ethhdr = mbufpullhdr(m, *ethhdr);
  if (!ethhdr) {
    mbuffree(m);
    return;
  }

  type = ntohs(ethhdr->type);
  if (type == ETHTYPE_IP)
    net_rx_ip(m);
  else if (type == ETHTYPE_ARP)
    net_rx_arp(m);
  else
    mbuffree(m);
}
